﻿using DirectShowLib;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;

namespace SenrobCapture
{
    public class VideoCapture: System.Windows.Forms.Panel
    {
        public const int WM_GRAPHNOTIFY = 0x8000 + 1;

        IGraphBuilder gb ;
        ICaptureGraphBuilder2 cgb ;
        IMediaControl mc ;
        IVideoWindow vw ;
        IMediaEventEx mex;
        IBaseFilter bf;
        DsROTEntry rot;

        enum CaptureState {Error, Play, Stop};
        CaptureState cs = CaptureState.Stop;
        DsDevice[] devices;

        Control host;

        Thread vidThread;

        public VideoCapture(System.Windows.Forms.Control handle)
        {
            host = handle;
            OpenCaptureDevice(null);
            this.Dock = DockStyle.Fill;
            MenuItem[] items = new MenuItem[devices.Length];
            for (int a = 0; a < devices.Length; a++)
            {
                items[a] = new MenuItem(devices[a].Name, contextClick);
                items[a].Tag = a.ToString();
            }
            ContextMenu cm = new ContextMenu(items);
            this.host.ContextMenu = cm;
            SetSize(this.host.Width, this.host.Height);
        }

        

        public void contextClick(object source, EventArgs a)
        {
            int id = int.Parse(((MenuItem)(source)).Tag.ToString());
            Close();
            OpenCaptureDevice(devices[id]);
            Play();
        }

        private void OpenCaptureDevice(DsDevice dev)
        {
            try
            {
                gb = (IGraphBuilder)new FilterGraph();
                cgb = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
                mc = (IMediaControl)gb;
                vw = (IVideoWindow)gb;
                mex = (IMediaEventEx)gb;

                // int hr = mex.SetNotifyWindow(host.Handle, WM_GRAPHNOTIFY, IntPtr.Zero);
                int hr = mex.SetNotifyWindow(this.Handle, WM_GRAPHNOTIFY, IntPtr.Zero);
                DsError.ThrowExceptionForHR(hr);

                hr = cgb.SetFiltergraph(gb);
                DsError.ThrowExceptionForHR(hr);

                if (dev == null)
                {
                    devices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
                    BindDevice(devices[0]);
                }
                else
                    BindDevice(dev);
            }
            catch (Exception e)
            {
                MessageBox.Show("Error starting video feed", "Video Error");
            }
        
        }

        public void SetOwner(Control host)
        {
            this.host = host;
            this.vw.put_Owner(host.Handle);
            vw.put_MessageDrain(host.Handle);
        }

        private void BindDevice(DsDevice dev)
        {
            // DsDevice dev = (DsDevice)devices[0];
            object source;

            Guid iid = typeof(IBaseFilter).GUID;
            dev.Mon.BindToObject(null, null, ref iid, out source);
            bf = (IBaseFilter)source;

            int hr = gb.AddFilter(bf, "Video Capture");
            DsError.ThrowExceptionForHR(hr);

            hr = cgb.RenderStream(PinCategory.Preview, MediaType.Video, bf, null, null);
            DsError.ThrowExceptionForHR(hr);

            Marshal.ReleaseComObject(bf);

            hr = this.vw.put_Owner(host.Handle);
            DsError.ThrowExceptionForHR(hr);

            hr = vw.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipChildren);
            DsError.ThrowExceptionForHR(hr);

            vw.SetWindowPosition(0, 0, this.ClientSize.Width, this.ClientSize.Height);
            // vw.put_MessageDrain(host.Handle);

            hr = this.vw.put_Visible(OABool.True);
            DsError.ThrowExceptionForHR(hr);

            rot = new DsROTEntry(this.gb);
        }

        public void Play()
        {
            try
            {
                if (vidThread != null)
                    vidThread = null;
                vidThread = new Thread(RunVideoThread);
                vidThread.Priority = ThreadPriority.Lowest;
                vidThread.IsBackground = true;
                vidThread.Start();
            }
            catch (Exception e)
            {
                MessageBox.Show("Unable to start video feed: \n" + e.Message, "Video Error " + e.ToString());
            }
        }

        public void RunVideoThread()
        {
            int hr = this.mc.Run();
            try
            {
                DsError.ThrowExceptionForHR(hr);
            }
            catch (Exception e)
            {
                MessageBox.Show("Could not open video feed\n" + e.Message, "Video Error");
            }
            cs = CaptureState.Play;
        }

        public void Stop()
        {

            int hr = this.mc.Stop();
            DsError.ThrowExceptionForHR(hr);
            cs = CaptureState.Stop;
        }

        public void SetSize(int w, int h)
        {
            int x = 0, y = 0;
            // Correct aspect ratio
            if (w / 16.0 > h / 9.0)
            {
                x = (int)((w - h / 9.0 * 16.0) / 2);
                w = (int)(h / 9.0 * 16.0);
            }
            else
            {
                y = (int)((h - w / 16.0 * 9.0) / 2);
                h = (int)(w / 16.0 * 9.0);
            }
            vw.SetWindowPosition(x, y, w, h);
        }
        public void Close()
        {
            if (mc != null)
                mc.StopWhenReady();
            cs = CaptureState.Stop;
            if (mex != null)
                mex.SetNotifyWindow(IntPtr.Zero, WM_GRAPHNOTIFY, IntPtr.Zero);
            if (vw != null)
            {
                vw.put_Visible(OABool.False);
                vw.put_Owner(IntPtr.Zero);
            }
            if (rot != null)
            {
                rot.Dispose();
                rot = null;
            }
            Marshal.ReleaseComObject(mc); mc = null;
            Marshal.ReleaseComObject(mex); mex = null;
            Marshal.ReleaseComObject(vw); vw = null;
            Marshal.ReleaseComObject(gb); gb = null;
            Marshal.ReleaseComObject(cgb); cgb = null;
        }

        public void HandleGraphEvent()
        {
            int hr = 0;
            EventCode evCode;
            IntPtr evParam1, evParam2;

            if (this.mex == null)
                return;

            while (this.mex.GetEvent(out evCode, out evParam1, out evParam2, 0) == 0)
            {
                // Free event parameters to prevent memory leaks associated with
                // event parameter data.  While this application is not interested
                // in the received events, applications should always process them.
                hr = this.mex.FreeEventParams(evCode, evParam1, evParam2);
                DsError.ThrowExceptionForHR(hr);
                
                // Insert event processing code here, if desired
            }
        }

        bool shown = false;
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case WM_GRAPHNOTIFY:
                    HandleGraphEvent();
                    break;
                default:
                    break;
            }
            
            // Pass this message to the video window for notification of system changes
            if (this.vw != null)
                this.vw.NotifyOwnerMessage(m.HWnd, m.Msg, m.WParam, m.LParam);

            base.WndProc(ref m);
        }

        public void Fullscreen(bool on)
        {
            if(on)
                vw.put_FullScreenMode(OABool.True);
            else
                vw.put_FullScreenMode(OABool.False);
        }
    }
}